package com.example.user.service.bootstrap.security.filter;

import com.example.user.service.application.service.TokenBlacklistService;
import com.example.user.service.common.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 * JWT认证过滤器 - Spring Security认证体系的核心组件
 * 
 * 【什么是过滤器？】
 * 过滤器(Filter)是Java Web开发中的重要组件，它像一个"检查站"，在请求到达Controller之前
 * 对每个HTTP请求进行预处理和验证。想象成机场安检，每个乘客(请求)都必须通过安检(过滤器)
 * 才能登机(到达Controller)。
 * 
 * 【JWT认证过滤器的作用】
 * 1. 拦截所有HTTP请求
 * 2. 检查请求头中是否包含有效的JWT Token
 * 3. 验证Token的合法性(是否过期、签名是否正确)
 * 4. 如果Token有效，将用户信息设置到Spring Security上下文中
 * 5. 如果Token无效或不存在，让请求继续，由其他安全机制处理
 * 
 * 【为什么继承OncePerRequestFilter？】
 * OncePerRequestFilter确保每个请求只被过滤一次，避免重复验证造成性能问题。
 * 在复杂的Web应用中，一个请求可能会经过多个过滤器，这个基类保证了我们的
 * JWT验证逻辑只执行一次。
 * 
 * 【Spring Security认证流程】
 * 1. 用户发送请求 → 2. JWT过滤器验证Token → 3. 设置认证上下文 → 4. 后续代码可以获取用户信息
 * 
 * 【注解说明】
 * @Slf4j: Lombok注解，自动生成日志对象log，用于记录调试和错误信息
 * @Component: Spring注解，将该类注册为Spring容器管理的Bean，可以被自动注入
 */
@Slf4j
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    /**
     * JWT工具类 - Token操作的核心工具
     * 
     * 【作用说明】
     * 负责JWT Token的生成、解析、验证等所有操作
     * 
     * 【@Autowired注解】
     * Spring的依赖注入注解，Spring容器会自动找到JwtUtil类型的Bean并注入到这里
     * 这样我们就不需要手动创建JwtUtil对象，Spring帮我们管理对象的生命周期
     */
    @Autowired
    private JwtUtil jwtUtil;
    
    /**
     * Token黑名单服务 - 管理已失效的JWT Token
     * 
     * 【作用说明】
     * 用于解决JWT无状态特性导致的logout后token仍然有效的问题
     * 通过维护一个黑名单，记录已经失效的token
     * 
     * 【JWT无状态认证的挑战】
     * JWT设计为无状态，服务器不保存token信息，这带来了扩展性优势
     * 但也带来了问题：无法主动使token失效（如用户logout）
     * 
     * 【黑名单解决方案】
     * 1. 用户logout时，将token加入黑名单
     * 2. 每次验证token时，先检查是否在黑名单中
     * 3. 黑名单中的token被视为无效，拒绝访问
     * 
     * 【性能考虑】
     * 黑名单检查会增加一定的性能开销，但这是安全性的必要代价
     * 在生产环境中，建议使用Redis等高性能缓存来存储黑名单
     */
    @Autowired
    private TokenBlacklistService tokenBlacklistService;
    
    /**
     * Spring应用上下文 - Spring容器的入口
     * 
     * 【作用说明】
     * ApplicationContext是Spring容器的核心接口，通过它可以获取容器中的任何Bean
     * 
     * 【为什么需要它？】
     * 在某些情况下，我们需要动态获取Bean，而不是通过@Autowired静态注入
     * 特别是在解决循环依赖问题时，这种方式非常有用
     */
    @Autowired
    private ApplicationContext applicationContext;
    
    /**
     * 用户详情服务 - 用户信息加载器
     * 
     * 【设计说明】
     * 注意这里没有使用@Autowired注解，而是通过延迟加载的方式获取
     * 这是为了避免循环依赖问题：
     * JwtAuthenticationFilter需要UserDetailsService，
     * 而UserDetailsService的实现类可能也需要其他被JwtAuthenticationFilter保护的组件
     * 
     * 【延迟加载的好处】
     * 只有在真正需要时才获取Bean，避免启动时的循环依赖问题
     */
    private UserDetailsService userDetailsService;
    
    /**
     * 获取用户详情服务实例 - 延迟加载模式的实现
     * 
     * 【延迟加载模式(Lazy Loading)】
     * 这是一种设计模式，只有在真正需要对象时才创建或获取它
     * 类似于"用时再买"的概念，避免提前准备造成的资源浪费
     * 
     * 【解决循环依赖的核心方法】
     * 循环依赖问题：A需要B，B需要A，如果同时创建会造成死锁
     * 解决方案：A先创建，需要B时再去获取B，这样打破了循环
     * 
     * 【实现原理】
     * 1. 第一次调用时，userDetailsService为null，通过ApplicationContext获取Bean
     * 2. 后续调用直接返回已获取的实例，提高性能
     * 3. 这种方式叫做"单例模式 + 延迟初始化"
     * 
     * 【为什么不直接用@Autowired？】
     * 如果直接用@Autowired，Spring在启动时就要解决所有依赖关系
     * 可能会遇到循环依赖导致启动失败
     * 
     * @return UserDetailsService 用户详情服务实例
     */
    private UserDetailsService getUserDetailsService() {
        if (userDetailsService == null) {
            // 通过Spring容器动态获取Bean，避免循环依赖
            userDetailsService = applicationContext.getBean(UserDetailsService.class);
        }
        return userDetailsService;
    }

    /**
     * 过滤器核心方法 - JWT认证的完整流程实现
     * 
     * 【方法执行时机】
     * 每个HTTP请求到达Controller之前都会执行这个方法
     * 这是Spring Security认证链中的关键环节
     * 
     * 【完整的JWT认证流程】
     * 1. 提取Token：从HTTP请求头的Authorization字段中提取JWT Token
     * 2. 解析Token：使用JWT工具类解析Token，获取用户名
     * 3. 验证Token：检查Token是否过期、签名是否正确
     * 4. 加载用户：从数据库加载用户详细信息
     * 5. 创建认证：创建Spring Security认证对象
     * 6. 设置上下文：将认证信息存储到SecurityContext中
     * 7. 继续处理：调用过滤器链，让请求继续向下传递
     * 
     * 【Spring Security上下文的作用】
     * SecurityContext就像一个"身份证明"，一旦设置成功，后续的所有代码
     * 都可以通过SecurityContextHolder.getContext()获取当前用户信息
     * 
     * 【异常处理策略】
     * 如果JWT验证失败，不会阻止请求继续执行，而是清除认证上下文
     * 让Spring Security的其他机制（如返回401未授权）来处理
     * 
     * @param request HTTP请求对象，包含客户端发送的所有信息
     * @param response HTTP响应对象，用于向客户端发送响应
     * @param filterChain 过滤器链，用于调用下一个过滤器或最终的Controller
     * @throws ServletException Servlet相关异常
     * @throws IOException 输入输出异常
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        
        try {
            // 【步骤1：提取JWT Token】
            // 从HTTP请求头的Authorization字段中提取Token
            // 标准格式：Authorization: Bearer <token>
            String jwt = getJwtFromRequest(request);
            
            // 【步骤2：检查Token是否存在】
            // StringUtils.hasText()检查字符串是否不为null且不为空字符串
            // 如果没有Token，说明可能是匿名访问或登录请求
            if (StringUtils.hasText(jwt)) {
                // 【步骤2.5：检查Token是否在黑名单中】
                // 这是新增的安全检查，解决JWT无状态认证的logout问题
                // 如果token在黑名单中，说明用户已经logout，应该拒绝访问
                if (tokenBlacklistService.isBlacklisted(jwt)) {
                    log.warn("Token is blacklisted (user has logged out): {}", 
                            jwt.substring(0, Math.min(20, jwt.length())) + "...");
                    // 清除认证上下文，确保不会认证成功
                    SecurityContextHolder.clearContext();
                    // 继续过滤器链，让Spring Security返回401未授权
                    filterChain.doFilter(request, response);
                    return;
                }
                
                // 【步骤3：解析Token获取用户名】
                // 这一步会解析JWT的payload部分，提取用户名信息
                // 如果Token格式错误或签名无效，会抛出异常
                String username = jwtUtil.getUsernameFromToken(jwt);
                
                // 【步骤4：检查是否需要认证】
                // username != null: 确保成功从Token中提取到用户名
                // SecurityContextHolder.getContext().getAuthentication() == null: 确保当前请求还没有认证信息
                // 这样避免重复认证，提高性能
                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    // 【步骤5：加载用户详细信息】
                    // 通过用户名从数据库加载完整的用户信息（包括权限、角色等）
                    // 这里使用了延迟加载的UserDetailsService，避免循环依赖
                    UserDetails userDetails = getUserDetailsService().loadUserByUsername(username);
                    
                    // 【步骤6：验证Token的完整性】
                    // 不仅要检查Token格式，还要验证：
                    // 1. Token是否过期
                    // 2. 签名是否正确
                    // 3. Token中的用户名是否与数据库中的一致
                    if (jwtUtil.validateToken(jwt, userDetails.getUsername())) {
                        // 【步骤7：创建Spring Security认证对象】
                        // UsernamePasswordAuthenticationToken是Spring Security的标准认证对象
                        // 三个参数的含义：
                        // 1. principal: 主体，通常是用户详情对象
                        // 2. credentials: 凭据，JWT模式下不需要密码，设为null
                        // 3. authorities: 权限列表，从用户详情中获取
                        UsernamePasswordAuthenticationToken authentication = 
                            new UsernamePasswordAuthenticationToken(
                                userDetails,           // 用户详情（包含用户名、权限等）
                                null,                  // 凭据（JWT认证不需要密码）
                                userDetails.getAuthorities() // 用户权限列表
                            );
                        
                        // 【步骤8：设置认证详情】
                        // 添加额外的认证信息，如IP地址、Session ID等
                        // 这些信息在安全审计和日志记录中很有用
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        
                        // 【步骤9：设置到Spring Security上下文】
                        // 这是最关键的一步！将认证信息存储到SecurityContext中
                        // 后续的Controller、Service等都可以通过SecurityContextHolder获取当前用户信息
                        // 这就是Spring Security"记住"用户身份的机制
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                        
                        // 记录成功的认证日志，便于调试和监控
                        log.debug("JWT authentication successful for user: {}", username);
                    } else {
                        // Token验证失败，记录警告日志
                        // 可能的原因：Token过期、签名错误、用户名不匹配等
                        log.warn("JWT token validation failed for user: {}", username);
                    }
                }
            }
        } catch (Exception e) {
            // 【异常处理：安全优先原则】
            // 任何异常都不应该影响系统的安全性
            // 记录错误日志，便于排查问题
            log.error("Cannot set user authentication: {}", e.getMessage());
            // 清除认证上下文，确保不会使用错误或不完整的认证信息
            // 这是安全编程的重要原则：出错时选择更安全的状态
            SecurityContextHolder.clearContext();
        }
        
        // 继续过滤器链，让请求继续向下处理
        filterChain.doFilter(request, response);
    }

    /**
     * 从请求中提取JWT Token - HTTP标准认证头解析
     * 
     * 【HTTP Authorization头标准】
     * HTTP协议规定，认证信息应该放在Authorization请求头中
     * JWT认证的标准格式：Authorization: Bearer <token>
     * 
     * 【Bearer认证方案】
     * Bearer是OAuth 2.0规范中定义的认证方案
     * 意思是"持有者"，表示谁持有这个Token，谁就有相应的权限
     * 格式：Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
     * 
     * 【为什么要单独提取？】
     * 1. 代码复用：多个地方可能需要提取Token
     * 2. 职责分离：Token提取和Token验证是不同的职责
     * 3. 易于测试：可以单独测试Token提取逻辑
     * 
     * @param request HTTP请求对象，包含所有请求头信息
     * @return JWT Token字符串，如果不存在或格式错误则返回null
     */
    private String getJwtFromRequest(HttpServletRequest request) {
        // 从请求头中获取Authorization字段的值
        // 例如："Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
        String bearerToken = request.getHeader("Authorization");
        // 使用JwtUtil工具类提取实际的Token部分（去掉"Bearer "前缀）
        return jwtUtil.extractTokenFromHeader(bearerToken);
    }

    /**
     * 判断是否需要过滤 - 实现白名单机制
     * 
     * 【白名单机制的重要性】
     * 不是所有的接口都需要JWT认证，有些接口必须是公开的：
     * 1. 登录接口：用户还没有Token，怎么能要求提供Token？
     * 2. 注册接口：新用户注册时也没有Token
     * 3. 文档接口：开发时需要查看API文档
     * 
     * 【OncePerRequestFilter的设计】
     * OncePerRequestFilter提供了shouldNotFilter方法，让我们可以灵活控制
     * 哪些请求需要过滤，哪些不需要。返回true表示跳过过滤。
     * 
     * 【安全考虑】
     * 白名单要谨慎设置，只有真正需要公开访问的接口才能加入白名单
     * 过多的白名单会降低系统安全性
     * 
     * 【路径匹配策略】
     * 使用startsWith进行前缀匹配，这样可以匹配一类接口
     * 例如：/user/login 可以匹配 /user/login、/user/login?username=xxx 等
     * 
     * @param request HTTP请求对象
     * @return boolean 是否不需要过滤，true表示跳过JWT验证
     * @throws ServletException Servlet异常
     */
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        // 获取请求的URI路径
        String path = request.getRequestURI();
        
        // 【业务接口白名单】
        // /user/login: 用户登录接口，用户通过用户名密码获取Token
        // /user/register: 用户注册接口，新用户创建账号
        boolean isBusinessWhitelist = path.startsWith("/user/login") || 
                                     path.startsWith("/user/register");
        
        // 【文档接口白名单】
        // 这些是Swagger API文档相关的接口，开发阶段需要公开访问
        // /doc.html: Knife4j文档首页
        // /swagger-ui: Swagger UI界面
        // /v3/api-docs: OpenAPI 3.0规范的JSON文档
        // /webjars: 前端资源文件（CSS、JS等）
        boolean isDocWhitelist = path.startsWith("/doc.html") ||
                                path.startsWith("/swagger-ui") ||
                                path.startsWith("/v3/api-docs") ||
                                path.startsWith("/webjars");
        
        // 返回true表示不需要JWT验证，false表示需要验证
        return isBusinessWhitelist || isDocWhitelist;
    }
}